package com.bondar.task2;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class Buffer<T> {

	private T[] elements;
	private int end = 0;
	private int start = 0;
	private int filled = 0;
	private final Lock lock;
	private final Condition notFull;
	private final Condition notEmpty;

	@SuppressWarnings("unchecked")
	public Buffer(int N) {
		elements = (T[]) (new Object[N]);
		lock = new ReentrantLock();
		notFull = lock.newCondition();
		notEmpty = lock.newCondition();
	}

	public T get() throws InterruptedException {
		lock.lock();
		
		try {
			String threadName = Thread.currentThread().getName();
			while (isEmpty()) {
				System.out.println(String.format("%s: get wait", threadName));
				notEmpty.await();
			}
			// remove oldest one
			System.out.println(String.format("%s:  get at [%d]: %s", threadName, start, elements[start]));
			T element = elements[start];
			elements[start] = null;
			start = start + 1 >= size() ? 0 : start + 1;
			filled--;
			notFull.signalAll();

			System.out.println("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
			return element;
			
		} finally {
			lock.unlock();
		}
	}

	public void put(T element) throws InterruptedException {
		lock.lock();
		
		try {
			String threadName = Thread.currentThread().getName();
			while (isFull()) {
				System.out.println(threadName + ": buffer empty? "
						+ isEmpty() + " put wait, buffer free? " + (size() - filled)
						+ ", start: " + start + ", end: " + end);
				notFull.await();
			}

			elements[end] = element;
			filled++;
			end++;
			end = end >= size() ? 0 : end;
			System.out.println(String.format("%s: put at [%d]: %s", threadName, end, element));
			System.out.println(String.format("%s: buffer empty? %b", threadName, isEmpty()));
			notEmpty.signalAll();    
			System.out.println("##################################");
		
		} finally {
			lock.unlock();
		}
	}

	public boolean isEmpty() {
		return filled == 0 && start == end;
	}

	public boolean isFull() {
		return filled == elements.length;
	}

	public int size() {
		return elements.length;
	}
}
